﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using GurLoader;
using GurLoader.Service;
using GurLoader.Service.ImgurTaskBuilder;
using Imgur.Net;
using Moq;
using NUnit.Framework;

namespace GurLoaderWPF.Test.Service.ImgurTaskBuilder
{
    [TestFixture]
    public class DefaultImgurTaskBuilderServiceTest
    {

        private Mock<ImageProvider> ImageProvider { get; set; }
        private Mock<AlbumProvider> AlbumProvider { get; set; }

        [SetUp]
        public void SetUp()
        {
            ImageProvider = new Mock<ImageProvider>();
            AlbumProvider = new Mock<AlbumProvider>();
        }

        [Test]
        public void SendImagesTest()
        {
            ImageContent[] images = 
            {
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" )
            };

            ImageInfo[] imageInfos =
            {
                new ImageInfo("aaaa", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("bbbb", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("cccc", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("dddd", string.Empty, string.Empty, DateTime.Now, 20, 20)
            };

            // we expect that img service will have 4 calls to upload the 4 images
            for (int i = 0; i < images.Length; ++i)
            {
                ImageProvider.Setup(img => img.UploadImage(images[i].ImageData, null, null))
                    .Returns(imageInfos[i]).Verifiable();
            }

            Mock<Progress<IDelegatingCommandProgress>> progress = new Mock<Progress<IDelegatingCommandProgress>>();
            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();

            ImgurTaskBuilderService service = new ImgurTaskBuilderService( 
                new ImageProviderWrapper(ImageProvider.Object),
                new AlbumProviderWrapper(AlbumProvider.Object));

            CancellationTokenSource ctSource = new CancellationTokenSource();
            CancellationToken ct = ctSource.Token;

            Task<ImgurProcessResult> task = service.UploadImages(images, progress.Object, ct, true, mockDispatcher.Object, false);

            task.Start();

            task.Wait(1000 * 5);

            Assert.IsTrue(task.IsCompleted);

            ImageProvider.VerifyAll();
            AlbumProvider.VerifyAll();  
        }

        [Test]
        public void SendImagesUploadThrowsExceptionTest()
        {
            ImageContent[] images = 
            {
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" )
            };

            ImageInfo[] imageInfos =
            {
                new ImageInfo("aaaa", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("bbbb", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("cccc", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("dddd", string.Empty, string.Empty, DateTime.Now, 20, 20)
            };

            // we expect that img service will have 4 calls to upload the 4 images
            for (int i = 0; i < images.Length; ++i)
            {
                ImageProvider.Setup(img => img.UploadImage(images[i].ImageData, null, null))
                    .Throws(new Exception("UploadException")).Verifiable();
            }

            Mock<Progress<IDelegatingCommandProgress>> progress = new Mock<Progress<IDelegatingCommandProgress>>();
            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();

            ImgurTaskBuilderService service = new ImgurTaskBuilderService(
                new ImageProviderWrapper(ImageProvider.Object),
                new AlbumProviderWrapper(AlbumProvider.Object));

            CancellationTokenSource ctSource = new CancellationTokenSource();
            CancellationToken ct = ctSource.Token;

            Task<ImgurProcessResult> task = service.UploadImages(images, progress.Object, ct, true, mockDispatcher.Object, false);

            task.Start();

            task.Wait(1000 * 5);

            Assert.IsTrue(task.IsCompleted);
            Assert.IsInstanceOf(typeof (BatchImageUploadResult), task.Result);

            BatchImageUploadResult result = task.Result as BatchImageUploadResult;
            foreach (var child in result.Results)
            {
                Assert.IsNull(child);
            }

            ImageProvider.VerifyAll();
            AlbumProvider.VerifyAll();
        }


        [Test]
        public void SendImagesCancelTest()
        {
            ImageContent[] images =
            {
                new ImageContent(new Bitmap(20, 20), "test bitmap"),
                new ImageContent(new Bitmap(20, 20), "test bitmap"),
                new ImageContent(new Bitmap(20, 20), "test bitmap"),
                new ImageContent(new Bitmap(20, 20), "test bitmap")
            };

            ImageInfo[] imageInfos =
            {
                new ImageInfo("aaaa", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("bbbb", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("cccc", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("dddd", string.Empty, string.Empty, DateTime.Now, 20, 20)
            };

            ManualResetEvent mre = new ManualResetEvent(false);
            // we expect that img service will have 4 calls to upload the 4 images
            for (int i = 0; i < images.Length; ++i)
            {
                ImageProvider.Setup(img => img.UploadImage(images[i].ImageData, null, null))
                    .Callback(() => mre.WaitOne())
                    .Returns(imageInfos[i]).Verifiable();
            }

            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();
            mockDispatcher.Setup(
                x => x.Invoke(It.IsAny<Delegate>(), It.IsAny<DispatcherPriority>(), It.IsAny<object[]>()))
                .Callback<Delegate, DispatcherPriority, object[]>((d, pri, args) =>
                {
                    d.GetMethodInfo().Invoke(d.Target, args);
                });

            Mock<IProgress<IDelegatingCommandProgress>> progress = new Mock<IProgress<IDelegatingCommandProgress>>();
            List<IDelegatingCommandProgress> progressReports = new List<IDelegatingCommandProgress>();

            progress.Setup(pr => pr.Report(It.IsAny<IDelegatingCommandProgress>()))
                .Callback<IDelegatingCommandProgress>((d) =>
                {
                    progressReports.Add(d);
                });


            ImgurTaskBuilderService service = new ImgurTaskBuilderService(
                new ImageProviderWrapper(ImageProvider.Object),
                new AlbumProviderWrapper(AlbumProvider.Object));

            CancellationTokenSource ctSource = new CancellationTokenSource();
            CancellationToken ct = ctSource.Token;

            Task<ImgurProcessResult> task = service.UploadImages(images, progress.Object, ct, true,
                mockDispatcher.Object, false);

            task.Start();

            try
            {
                ctSource.Cancel();
                task.Wait(1000*10);
            }
            catch (AggregateException aggEx)
            {
                // get the last progress report and verify its cancelled status
                IDelegatingCommandProgress lastProgress = progressReports.Last();
                Assert.IsTrue(lastProgress.IsCancelled);
                Assert.AreEqual(4, lastProgress.Children.Count);
                foreach (var child in lastProgress.Children)
                {
                    Assert.IsTrue(child.IsCancelled);
                }
            }


            Assert.IsTrue(task.IsCompleted);
            ImageProvider.VerifyAll();
            mockDispatcher.VerifyAll();
        }

        [Test]
        public void SendAlbumCancelTest()
        {
            // images we will be "adding" to the album
            ImageContent[] images = 
            {
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" )
            };

            ImageInfo[] imageInfos =
            {
                new ImageInfo("aaaa", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("bbbb", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("cccc", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("dddd", string.Empty, string.Empty, DateTime.Now, 20, 20)
            };

            AlbumInfo albumResult = new AlbumInfo("NewAlbum", string.Empty, string.Empty, DateTime.Now, LayoutStyle.Blog,
                0, string.Empty, string.Empty);
            
            ManualResetEvent mre = new ManualResetEvent(false);
            
            // we expect that img service will have 4 calls to upload the 4 images
            for ( int i = 0; i < images.Length; ++i)
            {
                ImageProvider.Setup(img => img.UploadImage(images[i].ImageData, null, null)).Callback(() => mre.WaitOne())
                    .Returns(imageInfos[i]).Verifiable();
            }
            
            Mock<IProgress<IDelegatingCommandProgress>> progress = new Mock<IProgress<IDelegatingCommandProgress>>();
            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();
            mockDispatcher.Setup(
                x => x.Invoke(It.IsAny<Delegate>(), It.IsAny<DispatcherPriority>(), It.IsAny<object[]>()))
                .Callback<Delegate, DispatcherPriority, object[]>((d, pri, args) =>
                {
                    d.GetMethodInfo().Invoke(d.Target, args);
                });
            List<IDelegatingCommandProgress> progressReports = new List<IDelegatingCommandProgress>();

            progress.Setup(pr => pr.Report(It.IsAny<IDelegatingCommandProgress>())).Callback<IDelegatingCommandProgress>((d) =>
            {
                progressReports.Add(d);
            });

            ImgurTaskBuilderService service = new ImgurTaskBuilderService(
                new ImageProviderWrapper(ImageProvider.Object),
                new AlbumProviderWrapper(AlbumProvider.Object));
 
            CancellationTokenSource ctSource = new CancellationTokenSource();
            CancellationToken ct = ctSource.Token;

            Task<ImgurProcessResult> task = service.UploadAlbum(images, string.Empty, string.Empty, LayoutStyle.Blog, progress.Object, ct, true, mockDispatcher.Object, false);
            task.Start();

            try
            {
                ctSource.Cancel();
                task.Wait(1000 * 10);
            }
            catch (AggregateException aggEx)
            {
                // get the last progress report and verify its cancelled status
                IDelegatingCommandProgress lastProgress = progressReports.Last();
                Assert.IsTrue(lastProgress.IsCancelled);
                Assert.AreEqual(4, lastProgress.Children.Count);
                foreach (var child in lastProgress.Children)
                {
                    Assert.IsTrue(child.IsCancelled);
                }
            }

            Assert.IsTrue(task.IsCompleted);

            ImageProvider.VerifyAll();
            AlbumProvider.VerifyAll();
            mockDispatcher.VerifyAll();
        }

        [Test]
        public void SendAlbumTest()
        {
            // images we will be "adding" to the album
            ImageContent[] images = 
            {
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" ),
                new ImageContent( new Bitmap(20, 20), "test bitmap" )
            };

            ImageInfo[] imageInfos =
            {
                new ImageInfo("aaaa", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("bbbb", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("cccc", string.Empty, string.Empty, DateTime.Now, 20, 20),
                new ImageInfo("dddd", string.Empty, string.Empty, DateTime.Now, 20, 20)
            };

            AlbumInfo albumResult = new AlbumInfo("NewAlbum", string.Empty, string.Empty, DateTime.Now, LayoutStyle.Blog,
                0, string.Empty, string.Empty);

            // we expect that img service will have 4 calls to upload the 4 images
            for (int i = 0; i < images.Length; ++i)
            {
                ImageProvider.Setup(img => img.UploadImage(images[i].ImageData, null, null))
                    .Returns(imageInfos[i]).Verifiable();
            }

            // we expect that the album service will have 1 call to create the album
            AlbumProvider.Setup(alb => alb.CreateAlbum(It.Is<List<String>>(l => l.Count == 4), string.Empty, string.Empty, PrivacyLevel.Public,
                LayoutStyle.Blog, null)).Returns(albumResult).Verifiable();

            Mock<Progress<IDelegatingCommandProgress>> progress = new Mock<Progress<IDelegatingCommandProgress>>();
            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();
            ImgurTaskBuilderService service = new ImgurTaskBuilderService(
                new ImageProviderWrapper(ImageProvider.Object),
                new AlbumProviderWrapper(AlbumProvider.Object));

            CancellationTokenSource ctSource = new CancellationTokenSource();
            CancellationToken ct = ctSource.Token;

            Task<ImgurProcessResult> task = service.UploadAlbum(images, string.Empty, string.Empty, LayoutStyle.Blog, progress.Object, ct, true, mockDispatcher.Object, false);
            task.Start();

            task.Wait(1000 * 5);

            Assert.IsTrue(task.IsCompleted);

            ImageProvider.VerifyAll();
            AlbumProvider.VerifyAll();
            mockDispatcher.VerifyAll();
        }
    }
}
